// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Linq;
using System.Reflection;
using CIP4.Item;

namespace CIP4.Serializer
{
    public class TypeContextType
    {
        private Type _originalType;
        public Type OriginalType 
        { 
            get
            {
                return _originalType;
            }
            set
            {
                _originalType = value;
                if (_originalType.GetInterface("IList`1") != null || _originalType.Name == "HashSet`1")
                {
                    Type parameterType = null;
                    if (_originalType.IsGenericType)
                        parameterType = _originalType.GetGenericArguments()[0];
                    else if (_originalType.IsArray)
                        parameterType = _originalType.GetElementType();
                    else
                        foreach (Type interfaceType in _originalType.GetInterfaces())
                        {
                            if (interfaceType.IsGenericType)
                            {
                                parameterType = interfaceType.GetGenericArguments()[0];
                                break;
                            }
                        }
                    if (parameterType == null)
                        throw new ArgumentException(string.Format("Unable to determine the parameter type for {0}", _originalType));
                    if (GetHostTypeName(parameterType) == "CIP4ReferenceType")
                    {
                        TheTypeName = "CIP4ReferenceType";
                        TheTypeSpecificName = TheTypeName;
                        TheType = GetActualType(parameterType);
                        HostTypeName = "CIP4ReferenceType";
                        TypeSerializerName = TheType.Name + "_Serializer";
                    }
                    else
                    {
                        IsNullable = GetHostTypeName(parameterType) == "Nullable";
                        TheType = GetActualType(parameterType);
                        TheTypeName = TheType.Name;
                        TheTypeSpecificName = TheTypeName;
                        TypeSerializerName = TheTypeName + "_Serializer";
                        TheTypeNamespace = TheType.Namespace;
                        HostTypeName = GetHostTypeName(_originalType);
                    }
                }
                else if (_originalType.GetInterface("IDictionary") != null)
                {
                    IsNullable = GetHostTypeName(_originalType.GetGenericArguments()[1]) == "Nullable";
                    if (_originalType.IsGenericType)
                        TheType = GetActualType(_originalType.GetGenericArguments()[1]);
                    else
                        throw new ArgumentException(string.Format("Only generic Dictionaries are supported: {0}", _originalType));
                    TheTypeName = TheType.Name;
                    TheTypeSpecificName = TheTypeName;
                    TypeSerializerName = TheTypeName + "_Serializer";
                    TheTypeNamespace = TheType.Namespace;
                    HostTypeName = GetHostTypeName(_originalType);
                }
                else if (GetHostTypeName(_originalType) == "CIP4ReferenceType")
                {
                    TheType = _originalType.GetGenericArguments()[0];
                    if (CIP4ItemAttribute.GetAttribute(TheType) == null)
                        throw new ArgumentException(string.Format("Only item types (i.e. classes that have the CIP4ItemAttribute) can be used as CIP4ReferenceType types: {0}", _originalType));
                    TheTypeName = TheType.Name;
                    TheTypeSpecificName = TheTypeName;
                    TypeSerializerName = TheTypeName + "_Serializer";
                    TheTypeNamespace = TheType.Namespace;
                    HostTypeName = GetHostTypeName(_originalType);
                }
                else if (GetHostTypeName(_originalType) == "CIP4ScalablePropertyType")
                {
                    TheType = _originalType;
                    TheOtherType = GetActualType(_originalType.GetGenericArguments()[0]);
                    TheTypeName = string.Format("CIP4ScalablePropertyType<{0}>", TheOtherType.FullName);
                    TheTypeSpecificName = "CIP4ScalablePropertyType";
                    TheTypeNamespace = "CIP4.Simple";
                    HostTypeName = GetHostTypeName(_originalType);
                    TypeSerializerName = HostTypeName + "_Serializer_" + TheOtherType.Name;
                }
                else if (_originalType.IsGenericType && GetHostTypeName(_originalType) != "Nullable")
                {
                    TheType = _originalType;
                    TheTypeName = GetHostTypeName(_originalType) + "<" + GetActualType(_originalType).FullName + ">";
                    TheTypeSpecificName = GetActualType(_originalType).FullName;
                    TypeSerializerName = GetHostTypeName(_originalType) + "_Serializer_" + GetActualType(_originalType).Name;
                    TheTypeNamespace = TheType.Namespace;
                    HostTypeName = GetHostTypeName(_originalType);
                    TheOtherType = GetActualType(_originalType);
                }
                else
                {
                    TheType = GetActualType(_originalType);
                    TheTypeName = TheType.Name;
                    TheTypeSpecificName = TheTypeName;
                    TypeSerializerName = TheTypeName + "_Serializer";
                    if (string.IsNullOrEmpty(TheType.FullName))
                        throw new ApplicationException("Type must have a full name");
                    TheTypeNamespace =
                        TheType.FullName.Contains("+") ? 
                        TheType.FullName.Substring(0, TheType.FullName.IndexOf("+")) : TheType.Namespace;
                    HostTypeName = GetHostTypeName(_originalType);
                }
            }
        }

        public Type TheType { get; private set; }

        /// <summary>
        /// If the OriginalType is generic and the type has been seen in the context of a property
        /// where the generic type parameter has been supplied - this will be the generic type parameter.
        /// </summary>
        public Type TheOtherType { get; private set; }

        public string TheTypeName { get; private set; }

        public string TheTypeSpecificName { get; private set; }

        public string TypeSerializerName { get; private set; }

        public string TheTypeFullName { get { return TheTypeNamespace + "." + TheTypeName; } }

        public string TheTypeNamespace { get; private set; }

        public string HostTypeName { get; set; }

        public bool HasExternalID
        {
            get
            {
                foreach (PropertyInfo prop in TheType.GetProperties())
                {
                    CIP4ExternalIDAttribute att = CIP4ExternalIDAttribute.GetAttribute(prop);
                    if (att != null)
                        return true;
                }
                return false;
            }

        }

        /// <summary>
        /// For an SV type means the type was declared as nullable, e.g. int?
        /// For an MV type means the generic type parameter was declared as nullable, e.g. List<int?>
        /// </summary>
        public bool IsNullable { get; set; }

        private PropertyInfo _versionProperty;
        private bool _alreadyLookedForAVersionProperty;
        public PropertyInfo VersionProperty
        {
            get
            {
                if (!_alreadyLookedForAVersionProperty && _versionProperty == null)
                    _versionProperty = TheType.GetProperties().FirstOrDefault(p => p.PropertyType.Name == typeof(CIP4ItemVersionsType).Name);
                _alreadyLookedForAVersionProperty = true;
                return _versionProperty;
            }

        }
        public TypeContextType()
        {
        }

        public TypeContextType(Type aTYpe)
        {
            OriginalType = aTYpe;
        }

        public override string ToString()
        {
            return TheType.FullName + (TheType.IsGenericType ? ("'" + TheOtherType.FullName) : string.Empty);
        }

        private static string GetHostTypeName(Type t)
        {
            if (t.IsGenericType)
                return t.Name.Substring(0, t.Name.IndexOf('`'));
            return t.Name;
        }

        private static Type GetActualType(Type t)
        {
            Type actualType = t;
            if (t.IsArray)
                actualType = t.GetElementType();

            if (actualType == null)
                throw new ApplicationException("Unexpected null type");

            if (Nullable.GetUnderlyingType(actualType) != null)
                actualType = Nullable.GetUnderlyingType(actualType);

            if (actualType.IsGenericType)
            {
                Type[] types = actualType.GetGenericArguments();
                actualType = types[types.Length - 1];
            }
            return actualType;
        }

    }

}
